﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Reflection;
using System.Linq;
using RAD.LightOne;
using System.Threading;

namespace RAD.LightOne
{
    #region Documentation
    /// <summary>
    /// Classe responsável pelo processamento das instâncias de MyObject
    /// no que tange a extração dos dados de uma intância para a estrutura 
    /// de dados intermediária que facilita as operações em banco.
    /// ThreadSafe.
    /// </summary>
    #endregion Documentation
    internal static class InternalObjBuilder
    {
        private static Mutex readMutex;
        private static Mutex writeMutex;

        #region Constructors

        static InternalObjBuilder()
        {
            readMutex = new Mutex();
            writeMutex = new Mutex();
        }
        #endregion

        #region Methods

        #region Documentation
        /// <summary>
        /// Retorna campos para ID e ClassName, em formato para operações com o banco de dados.
        /// </summary>
        #endregion
        private static Field[] GetKeyFieldsToWrite(Type type, IMyObject instance, bool makeLastPersistence)
        {
            IdAttribute idAttr = IdAttribute.GetDefinedAttribute(type);
            Field idField = CreateFieldToWrite(idAttr, type, instance, "id");

            ClassNameAttribute classAttr = ClassNameAttribute.GetDefinedAttribute(type);
            Field clsField = CreateFieldToWrite(classAttr, type, instance, "className");

            if (makeLastPersistence)
            {
                LastPersistenceAttribute lstAttr = LastPersistenceAttribute.GetDefinedAttribute(type);
                Field lstField = CreateFieldToWrite(lstAttr, type, instance, "lastPersistence");

                return new Field[] { idField, clsField,lstField };
            }

            return new Field[] { idField, clsField };
        }

        #region Documentation
        /// <summary>
        /// Recupero os campos chave num formato para leitura.
        /// </summary>
        #endregion
        private static Field[] GetKeyFieldsToRead(Type type, bool makeLastPersistence)
        {
            IdAttribute idAttr = IdAttribute.GetDefinedAttribute(type);
            Field idField = CreateFieldToRead(idAttr, type, "id");

            ClassNameAttribute classAttr = ClassNameAttribute.GetDefinedAttribute(type);
            Field clsField = CreateFieldToRead(classAttr, type, "className");

            if (makeLastPersistence)
            {
                LastPersistenceAttribute lstAttr = LastPersistenceAttribute.GetDefinedAttribute(type);
                Field lstField = CreateFieldToRead(lstAttr, type, "lastPersistence");

                return new Field[] { idField, clsField, lstField };
            }

            return new Field[] { idField, clsField };
        }

        #region Documentation
        /// <summary>
        /// Método auxiliar a GetKeyFieldsToWrite.
        /// </summary>
        #endregion
        internal static Field CreateFieldToWrite(FieldNameAttribute attribute, Type type, IMyObject instance, string classField)
        {
            FieldInfo idInfo = type.GetField(classField, Global.BINDING_FLAGS);
            Field idField = new Field(attribute.FieldName);
            idField.NameOnObject = idInfo.Name;
            idField.Operations = DataOperations.All;
            idField.Value = idInfo.GetValue(instance);
            return idField;
        }

        #region Documentation
        /// <summary>
        /// Método auxiliar a GetKeyFieldsToRead.
        /// </summary>
        #endregion
        private static Field CreateFieldToRead(FieldNameAttribute attribute, Type type, string classField)
        {
            FieldInfo idInfo = type.GetField(classField, Global.BINDING_FLAGS);
            Field idField = new Field(attribute.FieldName);
            idField.NameOnObject = idInfo.Name;
            idField.Operations = DataOperations.All;
            return idField;
        }

        #region Documentation
        /// <summary>
        /// Retorna um IntermediaryObject, que representa as tabelas e seus campos de uma forma relacionada à instância passada.
        /// </summary>
        /// <typeparam name="T">Tipo genérico que deve obrigatoriamente implementar ILightConsumible.</typeparam>
        /// <param name="instance"> intância que terá seu conteúdo "extraído".</param>
        /// <param name="operations"> operação(ões) que se pretende realizar e para o qual os campos devem estar habilitados.</param>
        #endregion
        public static InternalObj GetWriteStructure<T>(T instance, DataOperations operations) where T : IMyObject
        {
            try
            {
                writeMutex.WaitOne();

                Type[] types = MetadataReader.GetHierarchy(instance.GetType(), typeof(T));

                InternalObj dbTables = new InternalObj(instance.GetType());
                List<Field> dbFields = new List<Field>();

                int counter = -1;
                foreach (Type type in types)
                {
                    #region Comments
                    /*
                 * Neste trecho, quando localizado o atributo DBTable, monto um item de tabela
                 * que receberá mais abaixo todos os DBFields encontrados até o momento e os
                 * próximos, até que seja encontrado um novo DBTable.
                 */
                    #endregion Comments
                    if (DBTableAttribute.IsDefined(type))
                    {
                        counter++;
                        Table tableItem = new Table(((DBTableAttribute[])type.GetCustomAttributes(typeof(DBTableAttribute), false))[0].TableName);
                        dbTables.Add(tableItem);
                        if (counter == 0)
                        {
                            dbTables[counter].AddRange(GetKeyFieldsToWrite(type, instance, true));
                        }
                        else
                        {
                            dbTables[counter].AddRange(GetKeyFieldsToWrite(type, instance, false));
                        }
                    }

                    #region Comments
                    /*
                 * Neste trecho de código, eu realizo uma varredura nos campos e propriedades à 
                 * busca de membros marcados com DBField, os quais vão compondo a lista que será passada
                 * a um DBTableItem.
                 */
                    #endregion Comments
                    {
                        FieldInfo[] fields = type.GetFields(Global.BINDING_FLAGS | BindingFlags.DeclaredOnly);
                        foreach (FieldInfo field in fields.Where(f => DBFieldAttribute.IsDefined(f)))
                        {
                            DBFieldAttribute attrib = (DBFieldAttribute)field.GetCustomAttributes(typeof(DBFieldAttribute), false)[0];
                            if ((attrib.DataOperations & operations) == operations)
                            {
                                Field fieldItem = new Field(attrib.FieldName);
                                fieldItem.NameOnObject = field.Name;
                                fieldItem.Operations = attrib.DataOperations;
                                fieldItem.Value = field.GetValue(instance);
                                dbFields.Add(fieldItem);
                            }

                        }
                        PropertyInfo[] properties = type.GetProperties(Global.BINDING_FLAGS | BindingFlags.DeclaredOnly);
                        foreach (PropertyInfo property in properties.Where(p => DBFieldAttribute.IsDefined(p)))
                        {

                            DBFieldAttribute attrib = (DBFieldAttribute)property.GetCustomAttributes(typeof(DBFieldAttribute), false)[0];
                            if ((attrib.DataOperations & operations) == operations)
                            {
                                Field fieldItem = new Field(attrib.FieldName);
                                fieldItem.NameOnObject = property.Name;
                                fieldItem.Operations = attrib.DataOperations;
                                fieldItem.Value = (property.CanRead ? property.GetValue(instance, null) : null);
                                dbFields.Add(fieldItem);
                            }

                        }
                    }

                    #region Comments
                    /*
                * Aqui, passo os campos localizados para o DBTableItem e limpo a relação de
                * campos para nova passagem.
                */
                    #endregion Comments
                    if (counter > -1)
                    {
                        dbTables[counter].AddRange(dbFields.ToArray());
                        dbFields.Clear();
                    }
                }

                #region Comments
                /*
             * Caso nenhum DBTable tenha sido encontrado e DBFields o tenham
             * crio uma tabela "virtual" cujo nome é o nome da classe para
             * acessar os DFieldItems gerados.
             */
                #endregion Comments
                if ((dbTables.Count == 0) && (dbFields.Count > 0))
                {
                    Table nullItem = new Table(instance.GetType().Name);
                    dbTables.Add(nullItem);
                    dbTables[0].AddRange(dbFields.ToArray());
                }

                return dbTables;
            }
            finally
            {
                writeMutex.ReleaseMutex();
            }
        }

        #region Documentation
        /// <summary>
        /// Retorna um IntermediaryObject, que representa as tabelas e seus campos de uma forma relacionada ao tipo passado, ou seja,
        /// apenas a estrutura do objeto intermediário, diferentemente de GetWriteStructure, este método é voltado a adaptar os dados do banco para
        /// a classe, portanto a repetição de Id e ClassName nas diversas tabelas é omitida
        /// </summary>
        /// <param name="startType">Tipo mais específico, do qual a busca se inicia pela hierarquia de tipos até a raiz, predefinida como MyObject.</param>
        /// <param name="operations"> operação(ões) que se pretende realizar e para o qual os campos devem estar habilitados.</param>
        #endregion
        public static InternalObj GetReadStructure(Type startType, DataOperations operations)
        {
            return InternalObjBuilder.GetReadStructure(startType, typeof(MyObject), operations);
        }

        #region Documentation
        /// <summary>
        /// Retorna um IntermediaryObject, que representa as tabelas e seus campos de uma forma relacionada ao tipo passado, ou seja,
        /// apenas a estrutura do objeto intermediário, diferentemente de GetWriteStructure, este método é voltado a adaptar os dados do banco para
        /// a classe, portanto a repetição de Id e ClassName nas diversas tabelas é omitida
        /// </summary>
        /// <param name="startType">Tipo mais específico, do qual a busca se inicia pela hierarquia de tipos até a raiz, predefinida como MyObject.</param>
        /// <param name="stopType">Tipo de parada da busca, deve estar abaixo na hierarquia de tipos e implementar MyObject.</param>
        /// <param name="operations"> operação(ões) que se pretende realizar e para o qual os campos devem estar habilitados.</param>
        #endregion
        public static InternalObj GetReadStructure(Type startType, Type stopType, DataOperations operations)
        {
            try
            {
                readMutex.WaitOne();

                Cache.VoidInternalObjCache cache = new RAD.LightOne.Cache.VoidInternalObjCache();
                if (cache.ContainsKey(startType.FullName)) return cache[startType.FullName];

                if (!(startType.IsSubclassOf(typeof(MyObject)))) throw new ArgumentException("startType don't inherits MyObject.");

                Type[] types = MetadataReader.GetHierarchy(startType, stopType);

                InternalObj dbTables = new InternalObj(startType);
                List<Field> dbFields = new List<Field>();

                int counter = -1;
                foreach (Type type in types)
                {
                    #region Comments
                    /*
                 * Neste trecho, quando localizado o atributo DBTable, monto um item de tabela
                 * que receberá mais abaixo todos os DBFields encontrados até o momento e os
                 * próximos, até que seja encontrado um novo DBTable.
                 */
                    #endregion Comments
                    if (DBTableAttribute.IsDefined(type))
                    {
                        counter++;
                        Table tableItem = new Table(((DBTableAttribute[])type.GetCustomAttributes(typeof(DBTableAttribute), false))[0].TableName);
                        dbTables.Add(tableItem);

                        //se tenho definição de DBTable, então devo gerar dados para os campos chave, id, className e lastPersistence.
                        dbFields.AddRange(GetKeyFieldsToRead(type, counter == 0));
                    }

                    #region Comments
                    /*
                 * Neste trecho de código, eu realizo uma varredura nos campos e propriedades à 
                 * busca de membros marcados com DBField, os quais vão compondo a lista que será passada
                 * a um DBTableItem.
                 */
                    #endregion Comments
                    {
                        FieldInfo[] fields = type.GetFields(Global.BINDING_FLAGS | BindingFlags.DeclaredOnly);
                        foreach (FieldInfo field in fields)
                        {
                            if (DBFieldAttribute.IsDefined(field))
                            {
                                DBFieldAttribute attrib = (DBFieldAttribute)field.GetCustomAttributes(typeof(DBFieldAttribute), false)[0];
                                if ((attrib.DataOperations & operations) == operations)
                                {
                                    Field fieldItem = new Field(attrib.FieldName);
                                    fieldItem.NameOnObject = field.Name;
                                    fieldItem.Operations = attrib.DataOperations;
                                    dbFields.Add(fieldItem);
                                }
                            }
                        }
                        PropertyInfo[] properties = type.GetProperties(Global.BINDING_FLAGS | BindingFlags.DeclaredOnly);
                        foreach (PropertyInfo property in properties)
                        {
                            if (DBFieldAttribute.IsDefined(property))
                            {
                                DBFieldAttribute attrib = (DBFieldAttribute)property.GetCustomAttributes(typeof(DBFieldAttribute), false)[0];
                                if ((attrib.DataOperations & operations) == operations)
                                {
                                    Field fieldItem = new Field(attrib.FieldName);
                                    fieldItem.NameOnObject = property.Name;
                                    fieldItem.Operations = attrib.DataOperations;
                                    dbFields.Add(fieldItem);
                                }
                            }
                        }
                    }

                    #region Comments
                    /*
                * Aqui, passo os campos localizados para o DBTableItem e limpo a relação de
                * campos para nova passagem.
                */
                    #endregion Comments
                    if (counter > -1)
                    {
                        dbTables[counter].AddRange(dbFields.ToArray());
                        dbFields.Clear();
                    }
                }

                #region Comments
                /*
             * Caso nenhum DBTable tenha sido encontrado e DBFields o tenham
             * crio uma tabela "virtual" cujo nome é o nome da classe para
             * acessar os DFieldItems gerados.
             */
                #endregion Comments
                if ((dbTables.Count == 0) && (dbFields.Count > 0))
                {
                    Table nullItem = new Table(startType.Name);
                    dbTables.Add(nullItem);
                    dbTables[0].AddRange(dbFields.ToArray());
                }
                cache.Add(dbTables);
                return dbTables;
            }
#if DEBUG
            catch (Exception except)
            {
                return null;
            }
#endif
            finally
            {
                readMutex.ReleaseMutex();
            }
        }

        #region Deprecated
        /*
        #region Documentation
        /// <summary>
        /// Método que "refaz" uma estrutura baseada em um tipo incorreto porém pertencente à mesma hierarquia de tipos, 
        /// desde que o correto esteja acima, ou seja, mais específico que o tipo utilizado.
        /// Desenhado para ser um método auxiliar nas tarefas de recuperação dos dados do banco para a classe,
        /// deve ser utilizado apenas nos métodos de recuperação.
        /// </summary>
        /// <param name="baseObj">estrutura com os dados que serão repassados à estrutura correta.</param>
        /// <param name="realType">tipo real para a confecção do objeto interno.</param>
        #endregion
        public static InternalObj Rebuild(InternalObj baseObj, Type realType)
        {            
            InternalObj relevantTotal = InternalObjBuilder.GetReadStructure(realType, DataOperations.Select);

            foreach (Table tItem in baseObj)
            {
                foreach (Field fItem in tItem)
                {
                    relevantTotal[tItem.Name][fItem.Name].AllowNull = fItem.AllowNull;
                    relevantTotal[tItem.Name][fItem.Name].Lenght = fItem.Lenght;
                    relevantTotal[tItem.Name][fItem.Name].Value = fItem.Value;
                }
            }

            return relevantTotal;
        }*/
        #endregion Deprecated
        #endregion
    }
}
